#include "vl_props.h"
#include "vl_prop_writer.h"
#include "vl_prop_reader.h"

using namespace VL;

namespace VL {


bool checkProp (const VLProp& a, const VLProp& b) {
	return a == b;
}

std::vector<VLProp>* pickList(const std::shared_ptr<void>& ptr) {
	return std::static_pointer_cast<std::vector<VLProp>, void>(ptr).get();
}

}

VLProp::VLProp(std::initializer_list<VLProp> props): 
	dataPointer(std::make_shared<std::vector<VLProp>>(props)), 
	extraPointer(nullptr), 
	type(VLPropType::Array),
	valueType(VLPropValueType::Void) {}


VLProp& VLProp::operator [] (size_t index) {
	if(this->type != VLPropType::Array) {
		if(index == 0) {
			// 转换为数组
			this->dataPointer = std::make_shared<std::vector<VLProp>>();
			this->type = VLPropType::Array;
			this->valueType = VLPropValueType::Void;
			auto vector = pickList(this->dataPointer);
			vector->push_back(VLProp());
			return vector->at(0);
		} else {
			throw VLPropExcetion(VL_PROP_ERROR_NOT_SUPPORT, "cannot cast array prop");
		}
	}
	auto vector = pickList(this->dataPointer);
	if(index > vector->size()) {
		throw VLPropExcetion(VL_PROP_ERROR_OUT_BOUND, "out of array range");
	} else if (index == vector->size()) {
		vector->push_back(VLProp());
		return vector->at(index);
	}
	return vector->at(index);
}
const VLProp& VLProp::operator [] (size_t index) const {
	if(this->type != VLPropType::Array) {
		return VLProp::emptyPtop;
	}

	auto vector = pickList(this->dataPointer);
	auto size = vector->size();
	if(index >= size) {
		return VLProp::emptyPtop;
	}

	return vector->at(index);
}

VLProp& VLProp::add(const VLProp& prop) {
	if(this->type != VLPropType::Array) {
		// 转换为数组
		this->dataPointer = std::make_shared<std::vector<VLProp>>();
		this->type = VLPropType::Array;
		this->valueType = VLPropValueType::Void;
		auto vector = pickList(this->dataPointer);
		vector->push_back(prop);
		return vector->at(0);
	}
	auto vector = pickList(this->dataPointer);
	auto size = vector->size();
	if(size != 0 && vector->at(size - 1).type == VLPropType::Empty) {
		vector->at(size - 1) = prop;
	} else {
		vector->push_back(prop);
	}
	return vector->back();
}

VLProp& VLProp::insert(size_t index, const VLProp& prop) {
	if(this->type != VLPropType::Array) {
		throw VLPropExcetion(VL_PROP_ERROR_NOT_SUPPORT, "prop is not array type");
	}
	auto vector = pickList(this->dataPointer);
	auto size = vector->size();
	if(index > size) {
		throw VLPropExcetion(VL_PROP_ERROR_OUT_BOUND, "out of array range");
	} else if(index == size) {
		if(size != 0 && vector->at(size - 1).type == VLPropType::Empty) {
			vector->at(size - 1) = prop;		
		} else {
			vector->push_back(prop);
		}
	} else {
		vector->emplace(vector->begin() + index, prop);
	}
	return vector->at(index);
}

void VLProp::_uniqueArr() {
	if(this->type != VLPropType::Array) {
		return;
	}

	auto vector = pickList(this->dataPointer);
	for(auto iter = vector->begin() ; iter != vector->end() ; ) {
		if(iter->type == VLPropType::Empty) {
			continue;
		}
		bool isEqual = false;
		for(auto checkIter = vector->begin() ; checkIter != iter ; checkIter ++) {
			if(checkIter->operator==(*iter)) {
				isEqual = true;
				break;
			}
		}
		if(isEqual) {
			vector->erase(iter);
		} else {
			iter->unique();
			iter ++;
		}
	}
	return;
}

const bool VLProp::_checkArrayEqual(const VLProp& prop) const {
	if(prop.type != VLPropType::Array) {
		return false;
	}
	auto vector = pickList(this->dataPointer);
	auto propVector = pickList(prop.dataPointer);
	if(vector->size() != propVector->size()) {
		return false;
	}
	for(auto iter = vector->begin() ; iter != vector->end() ; iter ++) {
		const auto & item = *iter;
		bool found = false;
		for(auto propIter = propVector->begin() ; propIter != propVector->end() ; propIter ++) {
			const auto & propItem = *propIter;
			if(item == propItem) {
				found = true;
				break;
			}
		}
		if(!found) {
			return false;
		}
	}
	return true;
}


const size_t VLProp::_getArraySize() const {
	auto vector = pickList(this->dataPointer);
	auto size = vector->size();
	if(size != 0 && vector->at(size - 1).type == VLPropType::Empty) {
		size --;
	}
	return size;
}

const void VLProp::_copyArray(VLProp& targetProp) const {
	auto vector = pickList(this->dataPointer);
	targetProp.dataPointer = std::make_shared<std::vector<VLProp>>(*vector);
	targetProp.extraPointer.reset();
	targetProp.type = VLPropType::Array;
	targetProp.valueType = VLPropValueType::Void;
}

const void VLProp::visitArr(VLPropArrayVistor visitor) const {
	if(visitor && this->type == VLPropType::Array) {
		auto vector = pickList(this->dataPointer);
		for(auto iter = vector->begin() ; iter != vector->end() ; iter ++) {
			if(iter->type == VLPropType::Empty) {
				continue;
			}
			if(!visitor(*iter)) {
				break;
			}
		}
	}
}

const void VLProp::_printArray(std::string margin) const {
	auto vector = pickList(this->dataPointer);
	for(auto iter = vector->begin() ; iter != vector->end() ; iter ++) {
		auto extra = iter->extraPointer.get();
		if(extra != nullptr) {
			for(auto iter = extra->begin() ; iter != extra->end() ; iter ++) {
				printf("%s # %s\n", margin.c_str(), iter->c_str());
			}
		}
		switch(iter->type) {
			case VLPropType::Node:
				printf("%s - ", margin.c_str());
				iter->_printNode("");
				printf("\n");
				break;
			case VLPropType::Array:
				printf("%s - []:\n", margin.c_str());
				iter->_printArray(margin + '\t');
				break;
			case VLPropType::Map:
				printf("%s - {}:\n", margin.c_str());
				iter->_printMap(margin + '\t');
				break;
			case VLPropType::Empty:
				break;
		}
	}
}

const void VLProp::_writeArrayVLC(VLPropWriter& writer) const {
	writer.write((uint8_t)this->type & 0xFF);
	auto extra = this->extraPointer.get();
	if(extra != nullptr) {
		auto extraSize = extra->size();
		writer.write(extraSize, 2);
		for(auto iter = extra->begin() ; iter != extra->end() ; iter ++) {
			writer.writeStrWithSize(*iter);
		}
	} else {
		writer.write(0, 2);
	}
	auto vector = pickList(this->dataPointer);
	auto size = vector->size();
	if(size != 0 && vector->at(size - 1).type == VLPropType::Empty) {
		size --;
	}
	writer.write(size, 2);
	for(auto iter = vector->begin() ; iter != vector->end() ; iter ++) {
		switch(iter->type) {
			case VLPropType::Node:
				iter->_writeNodeVLC(writer);
				break;
			case VLPropType::Array:
				iter->_writeArrayVLC(writer);
				break;
			case VLPropType::Map:
				iter->_writeMapVLC(writer);
				break;
			case VLPropType::Empty:
				break;
		}
	}
}

const void VLProp::_readArrayVLC(VLPropReader& reader) {
	this->dataPointer = std::make_shared<std::vector<VLProp>>();
	this->extraPointer.reset();
	this->type = VLPropType::Array;
	this->valueType = VLPropValueType::Void;
	this->_readExtraVLC(reader);
	auto vector = pickList(this->dataPointer);
	auto size = reader.readNumber<uint16_t>();
	VLProp tempProp;
	for(uint16_t i = 0 ; i < size ; i ++) {
		VLProp::readVLC(reader, tempProp);
		vector->push_back(tempProp);
	}
}

const void VLProp::_writeArrayVLP(VLPropWriter& writer, std::string margin) const {
	auto vector = pickList(this->dataPointer);
	for(auto iter = vector->begin() ; iter != vector->end() ; iter ++) {
		auto extra = iter->extraPointer.get();
		if(extra != nullptr) {
			for(auto iter = extra->begin() ; iter != extra->end() ; iter ++) {
				writer.writeStr("%s # %s\n", margin.c_str(), iter->c_str());
			}
		}
		switch(iter->type) {
			case VLPropType::Node:
				writer.writeStr("%s - ", margin.c_str());
				iter->_writeNodeVLP(writer, "");
				writer.write('\n');
				break;
			case VLPropType::Array:
				writer.writeStr("%s - []:\n", margin.c_str());
				iter->_writeArrayVLP(writer, margin + '\t');
				break;
			case VLPropType::Map:
				writer.writeStr("%s - {}:\n", margin.c_str());
				iter->_writeMapVLP(writer, margin + '\t');
				break;
			case VLPropType::Empty:
				break;
		}
	}
}
